package net.oschina.gitapp.api;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.SSLHandshakeException;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.OptionsMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.TraceMethod;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.io.IOUtils;
import org.apache.http.protocol.HTTP;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import net.oschina.gitapp.AppContext;
import net.oschina.gitapp.AppException;
import net.oschina.gitapp.bean.URLs;

/**
 * gitlabApi网络请求类 Gitlab HTTP Requestor
 * 
 * Responsible for handling HTTP requests to the Gitlab API
 * 
 * @author @timols 最后更新时间：2014-05-16 更新者：火蚁
 */
public class HTTPRequestor {
    
    public static final byte GET_METHOD = 0x00;
    public static final byte POST_METHOD = 0x01;
    public static final byte PUT_METHOD = 0x02;
    public static final byte PATCH_METHOD = 0x03;
    public static final byte DELETE_METHOD = 0x04;
    public static final byte HEAD_METHOD = 0x05;
    public static final byte OPTIONS_METHOD = 0x06;
    public static final byte TRACE_METHOD = 0x07;
    
    public static final String UTF_8 = "UTF-8";
    public static final String DESC = "descend";
    public static final String ASC = "ascend";
    
    public final static int TIMEOUT_CONNECTION = 20000;// 连接超时时间
    public final static int TIMEOUT_SOCKET = 20000;// socket超时
    
    private AppContext mContext;
    
    private String url;
    
    // 网络请求agent
    private static String appUserAgent;
    // 请求client
    private HttpClient _httpClient;
    // 请求方法类型
    private byte _methodType;
    // 请求方法
    private HttpMethod _method;
    // private String _method = "GET"; // 默认用GET方式请求
    private Map<String, Object> _data = new HashMap<String, Object>();// 请求表单参数
    
    private enum METHOD {
        GET, PUT, POST, PATCH, DELETE, HEAD, OPTIONS, TRACE;
        
        public static String prettyValues() {
            METHOD[] methods = METHOD.values();
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < methods.length; i++) {
                METHOD method = methods[i];
                builder.append(method.toString());
                
                if (i != methods.length - 1) {
                    builder.append(", ");
                }
            }
            return builder.toString();
        }
    }
    
    /**
     * Sets the HTTP Request method for the request.
     * 
     * Has a fluent api for method chaining.
     * 
     * @param method
     *            The HTTP method
     * @return this
     */
    public HTTPRequestor init(AppContext appContext, byte methodType, String url) {
        _methodType = methodType;
        _httpClient = getHttpClient();
        
        this.mContext = appContext;
        this.url = url;
        
        String urser_agent = appContext != null ? getUserAgent(appContext) : "";
        _method = getMethod(methodType, url, urser_agent);
        return this;
    }
    
    /**
     * 获得UserAgent
     * 
     * @param appContext
     * @return
     */
    private static String getUserAgent(AppContext appContext) {
        if (appUserAgent == null || appUserAgent == "") {
            StringBuilder ua = new StringBuilder("Git@OSC.NET");
            ua.append('/' + appContext.getPackageInfo().versionName + '_'
                    + appContext.getPackageInfo().versionCode);// App版本
            ua.append("/Android");// 手机系统平台
            ua.append("/" + android.os.Build.VERSION.RELEASE);// 手机系统版本
            ua.append("/" + android.os.Build.MODEL); // 手机型号
            ua.append("/" + appContext.getAppId());// 客户端唯一标识
            appUserAgent = ua.toString();
        }
        return appUserAgent;
    }
    
    /**
     * 获得一个http连接
     * 
     * @return
     */
    private static HttpClient getHttpClient() {
        HttpClient httpClient = new HttpClient();
        // 设置 HttpClient 接收 Cookie,用与浏览器一样的策略
        httpClient.getParams().setCookiePolicy(
                CookiePolicy.BROWSER_COMPATIBILITY);
        // 设置 默认的超时重试处理策略
        httpClient.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler());
        // 设置 连接超时时间
        httpClient.getHttpConnectionManager().getParams()
                .setConnectionTimeout(TIMEOUT_CONNECTION);
        // 设置 读数据超时时间
        httpClient.getHttpConnectionManager().getParams()
                .setSoTimeout(TIMEOUT_SOCKET);
        // 设置 字符集
        httpClient.getParams().setContentCharset(UTF_8);
        return httpClient;
    }
    
    private static HttpMethod getMethod(byte methodType, String url,
            String userAgent) {
        
        HttpMethod httpMethod = null;
        switch (methodType) {
            case GET_METHOD:
                httpMethod = new GetMethod(url);
                break;
            case POST_METHOD:
                httpMethod = new PostMethod(url);
                break;
            case PUT_METHOD:
                httpMethod = new PutMethod(url);
                break;
            case PATCH_METHOD:
                // httpMethod = new
                break;
            case DELETE_METHOD:
                httpMethod = new DeleteMethod(url);
                break;
            case HEAD_METHOD:
                httpMethod = new HeadMethod(url);
                break;
            case OPTIONS_METHOD:
                httpMethod = new OptionsMethod(url);
                break;
            case TRACE_METHOD:
                httpMethod = new TraceMethod(url);
                break;
            default:
                break;
        }
        if (null == httpMethod) {
            // 抛出一个不支持的请求方法
            throw new IllegalArgumentException("Invalid HTTP Method: UnKonwn"
                    + ". Must be one of " + METHOD.prettyValues());
        }
        // 设置 请求超时时间
        httpMethod.getParams().setSoTimeout(TIMEOUT_SOCKET);
        httpMethod.setRequestHeader("Host", URLs.HOST);
        httpMethod.setRequestHeader("Accept-Encoding", "gzip,deflate,sdch");
        httpMethod.setRequestHeader("Accept-Language",
                "zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4");
        httpMethod.setRequestHeader("Connection", "Keep-Alive");
        httpMethod.setRequestHeader(HTTP.USER_AGENT, userAgent);
        return httpMethod;
    }
    
    /**
     * 获取网络图片
     * 
     * @return
     * @throws AppException
     */
    public Bitmap getNetBitmap() throws AppException {
        Bitmap bitmap = null;
        InputStream inStream = getResponseBodyStream();
        bitmap = BitmapFactory.decodeStream(inStream);
        try {
            inStream.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }
    
    /**
     * 设置拼接请求的参数
     * 
     * @param key
     * @param value
     * @return this
     */
    public HTTPRequestor with(String key, Object value) {
        if (value != null && key != null) {
            _data.put(key, value);
        }
        return this;
    }
    
    public InputStream getResponseBodyStream() throws AppException {
        
        // 设置请求参数
        if (hasOutput()) {
            submitData(_data, _method);
        }
        
        InputStream responseBodyStream = null;
        try {
            int statusCode = _httpClient.executeMethod(_method);
            if (statusCode != HttpStatus.SC_OK
                    && !String.valueOf(statusCode).startsWith("2")) {
                uploadErrorToServer(
                        mContext,
                        url,
                        statusCode,
                        getMethod(_methodType) + "  "
                                + _method.getResponseBodyAsString() + "  "
                                + getUserAgent(mContext) + "  "
                                + getJsonString(_data));
                throw AppException.http(statusCode);
            }
            Header header = _method.getResponseHeader("Content-Encoding");
            if (header != null && header.getValue().equalsIgnoreCase("gzip")) {
                responseBodyStream = new GZIPInputStream(
                        _method.getResponseBodyAsStream());
            }
            else {
                responseBodyStream = new ByteArrayInputStream(
                        _method.getResponseBody());
            }
            
        }
        catch (HttpException e) {
            e.printStackTrace();
            // 发生致命的异常，可能是协议不对或者返回的内容有问题
            throw AppException.http(e);
        }
        catch (IOException e) {
            // 发生网络异常
            e.printStackTrace();
            throw AppException.network(e);
        }
        finally {
            // 释放连接
            releaseConnection();
        }
        return responseBodyStream;
    }
    
    public String getResponseBodyString() throws AppException {
        
        // 设置请求参数
        if (hasOutput()) {
            submitData(_data, _method);
        }
        
        String responseBodyString = null;
        GZIPInputStream gis = null;
        try {
            int statusCode = _httpClient.executeMethod(_method);
            if (statusCode != HttpStatus.SC_OK
                    && !String.valueOf(statusCode).startsWith("2")) {
                uploadErrorToServer(
                        mContext,
                        url,
                        statusCode,
                        getMethod(_methodType) + "  "
                                + _method.getResponseBodyAsString() + "  "
                                + getUserAgent(mContext) + "  "
                                + getJsonString(_data));
                throw AppException.http(statusCode);
            }
            Header header = _method.getResponseHeader("Content-Encoding");
            if (header != null && header.getValue().equalsIgnoreCase("gzip")) {
                try {
                    gis = new GZIPInputStream(_method.getResponseBodyAsStream());
                    responseBodyString = IOUtils.toString(gis);
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
                finally {
                    if (gis != null) {
                        try {
                            gis.close();
                        }
                        catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            else {
                responseBodyString = _method.getResponseBodyAsString();
            }
        }
        catch (HttpException e) {
            // 发生致命的异常，可能是协议不对或者返回的内容有问题
            e.printStackTrace();
            throw AppException.http(e);
        }
        catch (IOException e) {
            // 发生网络异常
            e.printStackTrace();
            throw AppException.network(e);
        }
        finally {
            // 释放连接
            releaseConnection();
        }
        return responseBodyString;
    }
    
    private void releaseConnection() {
        // 释放连接
        if (_method != null) {
            _method.releaseConnection();
        }
        if (_httpClient != null) {
            _httpClient = null;
        }
    }
    
    public <T> T to(T instance) throws AppException {
        return to(null, instance);
    }
    
    public <T> T to(Class<T> type) throws AppException {
        return to(type, null);
    }
    
    /**
     * 获取单个对象
     * 
     * @param type
     * @param instance
     * @return
     * @throws AppException
     */
    public <T> T to(Class<T> type, T instance) throws AppException {
        
        InputStream inputStream = getResponseBodyStream();
        try {
            return parse(inputStream, type, instance);
        }
        catch (IOException e) {
            throw AppException.io(e);
        }
    }
    
    /**
     * 获取一个列表数据
     * 
     * @param type
     * @return
     * @throws AppException
     */
    public <T> List<T> getList(Class<T[]> type) throws AppException {
        List<T> results = new ArrayList<T>();
        InputStream inputStream = getResponseBodyStream();
        if (inputStream == null) {
            // 抛出一个网络异常
            throw AppException.http(_method.getStatusLine().getStatusCode());
        }
        try {
            T[] _next = parse(inputStream, type, null);
            results.addAll(Arrays.asList(_next));
        }
        catch (IOException e) {
            throw AppException.io(e);
        }
        
        return results;
    }
    
    /**
     * 表单参数处理
     */
    private void submitData(Map<String, Object> data, HttpMethod method) {
        method.setRequestHeader("Content-Type",
                "application/x-www-form-urlencoded");
        if (method instanceof PostMethod) {
            List<NameValuePair> nvps = new ArrayList<NameValuePair>();
            if (data != null) {
                for (String name : data.keySet()) {
                    Object value = data.get(name);
                    NameValuePair nvp = new NameValuePair(name,
                            String.valueOf(value));
                    nvps.add(nvp);
                }
            }
            for (NameValuePair nameValuePair : nvps) {
                ((PostMethod) method).addParameter(nameValuePair);
            }
        }
        if (_method instanceof PutMethod) {
            Part parts[] = new Part[data.size()];
            int i = 0;
            for (String name : data.keySet()) {
                Object value = data.get(name);
                parts[i++] = new StringPart(name, String.valueOf(value));
            }
            ((PutMethod) _method).setRequestEntity(new MultipartRequestEntity(
                    parts, _method.getParams()));
        }
    }
    
    /**
     * 判断是否需要设置表单参数
     * 
     * @param methodType
     * @return
     */
    private boolean hasOutput() {
        return _methodType == POST_METHOD || _methodType == PUT_METHOD;
    }
    
    /**
     * 对获取到的网络数据进行处理
     * 
     * @param connection
     * @param type
     * @param instance
     * @return
     * @throws IOException
     */
    private <T> T parse(InputStream inputStream, Class<T> type, T instance)
            throws IOException {
        InputStreamReader reader = null;
        try {
            reader = new InputStreamReader(inputStream);
            String data = IOUtils.toString(reader);
            
            if (type != null) {
                return ApiClient.MAPPER.readValue(data, type);
            }
            else if (instance != null) {
                return ApiClient.MAPPER.readerForUpdating(instance).readValue(
                        data);
            }
            else {
                return null;
            }
        }
        catch (SSLHandshakeException e) {
            throw new SSLHandshakeException(
                    "You can disable certificate checking by setting ignoreCertificateErrors on GitlabHTTPRequestor");
        }
        finally {
            IOUtils.closeQuietly(reader);
        }
    }
    
    /**
     * 上传post请求错误到服务器日志上
     * 
     * @param appContext
     * @param errorUrl
     * @param errorCode
     * @param post_content
     */
    private void uploadErrorToServer(final AppContext appContext,
            final String errorUrl, final int errorCode,
            final String post_content) {
        new Thread() {
            public void run() {
                try {
                    HttpClient client = getHttpClient();
                    String url = URLs.URL_API_HOST + "app_logger";
                    HttpMethod post = getMethod(POST_METHOD, url,
                            getUserAgent(appContext));
                    Map<String, Object> data = new HashMap<String, Object>();
                    data.put("app_type", "android");
                    data.put("url", errorUrl);
                    data.put("error", errorCode);
                    data.put("post_content", post_content);
                    submitData(data, post);
                    client.executeMethod(post);
                }
                catch (HttpException e) {
                    e.printStackTrace();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
    
    private String getJsonString(Map<String, Object> data) {
        String res = "";
        if (data != null && data.size() > 0) {
            StringBuilder ua = new StringBuilder("{");
            int i = 0;
            for (String name : data.keySet()) {
                String value = (String) data.get(name);
                if (name.equalsIgnoreCase("password")) {
                    value = "*******";
                }
                i++;
                if (i != data.size()) {
                    ua.append("\"" + name + "\"" + ":" + value + ",");
                }
                else {
                    ua.append("\"" + name + "\"" + ":" + value);
                }
            }
            ua.append("}");
            res = ua.toString();
        }
        return res;
    }
    
    private String getMethod(byte method) {
        String res = "";
        switch (method) {
            case GET_METHOD:
                res = "GET";
                break;
            case POST_METHOD:
                res = "POST";
                break;
            case PUT_METHOD:
                res = "PUT";
                break;
            case PATCH_METHOD:
                res = "PATCH";
                break;
            case DELETE_METHOD:
                res = "DELETE";
                break;
            case HEAD_METHOD:
                res = "HEAD";
                break;
            case OPTIONS_METHOD:
                res = "OPTIONS";
                break;
            case TRACE_METHOD:
                res = "TRACE";
                break;
        }
        return res;
    }
    
    /**
     * 处理错误异常
     * 
     * @param e
     * @param connection
     * @throws Exception
     */
    /*
     * private AppException handleAPIError(Exception e, InputStream
     * errorInputStream) { AppException exception = null;
     * 
     * if (e instanceof FileNotFoundException) { return AppException.file(e); //
     * pass through 404 Not Found to allow the caller to handle it intelligently
     * } else if (e instanceof UnknownHostException || e instanceof
     * ConnectException) { return AppException.network(e); }
     * 
     * InputStream es = null; try { es = wrapStream(connection,
     * errorInputStream); //int code = connection.getResponseCode(); if (es !=
     * null) { exception = AppException.io((IOException) new
     * IOException(IOUtils.toString(es, "UTF-8")).initCause(e), code); } else {
     * exception = AppException.run(e); } } catch (IOException e1) {
     * e1.printStackTrace(); } finally { IOUtils.closeQuietly(es); } return
     * exception; }
     */
}
